package deanandjames.sprite;

import deanandjames.image.ImageLoader;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class BrickManager {
	private final static String IMAGE_DIR = "resources/images/";
	private final static int MAX_BRICKS_LINES = 15;
	private final static double MOVE_FACTOR = 0.25;
	private List<BufferedImage> brickImages = null;
	private final List<Brick> bricks;
	private List<Brick>[] columnBricks;
	private int height;
	private final ImageLoader imagesLoader;
	private int imWidth, imageHeight;
	private boolean isMovingLeft;
	private boolean isMovingRight;
	private int moveSize;
	private int numCols, numRows;
	private final int panelHeight;
	private final int panelWidth;
	private int width;
	private int xMapHead;

	public BrickManager(int width, int height, String fnm, ImageLoader imagesLoader) {
		panelWidth = width;
		panelHeight = height;
		this.imagesLoader = imagesLoader;
		bricks = new ArrayList<Brick>();
		loadBricksFile(fnm);
		initBricksInfo();
		createColumns();
		moveSize = (int) (imWidth * MOVE_FACTOR);
		if (moveSize == 0) {
			System.out.println("moveSize cannot be 0, setting it to 1");
			moveSize = 1;
		}
		isMovingRight = false;
		isMovingLeft = false;
		xMapHead = 0;
	}

	public int checkBrickBase(int xWorld, int yWorld, int step) {
		if (insideBrick(xWorld, yWorld)) {
			final int yMapWorld = yWorld - (panelHeight - height);
			final int mapY = yMapWorld / imageHeight;
			final int topOffset = yMapWorld - mapY * imageHeight;
			final int smallStep = step - (imageHeight - topOffset);
			return smallStep;
		}
		return step;
	}

	public int checkBrickTop(int xWorld, int yWorld, int step) {
		if (insideBrick(xWorld, yWorld)) {
			final int yMapWorld = yWorld - (panelHeight - height);
			final int mapY = yMapWorld / imageHeight;
			final int topOffset = yMapWorld - mapY * imageHeight;
			final int smallStep = step - topOffset;
			return smallStep;
		}
		return step;
	}

	public void display(Graphics g) {
		int brickCoord = xMapHead / imWidth * imWidth;
		int offset;
		if (brickCoord >= 0) {
			offset = xMapHead - brickCoord;
		} else {
			offset = brickCoord - xMapHead;
		}
		if (brickCoord >= 0 && brickCoord < panelWidth) {
			drawBricks(g, 0 - (imWidth - offset), xMapHead, width - brickCoord - imWidth);
			drawBricks(g, xMapHead, panelWidth, 0);
		} else if (brickCoord >= panelWidth) {
			drawBricks(g, 0 - (imWidth - offset), panelWidth, width - brickCoord - imWidth);
		} else if (brickCoord < 0 && brickCoord >= panelWidth - width + imWidth) {
			drawBricks(g, 0 - offset, panelWidth, -brickCoord);
		} else if (brickCoord < panelWidth - width + imWidth) {
			drawBricks(g, 0 - offset, width + xMapHead, -brickCoord);
			drawBricks(g, width + xMapHead, panelWidth, 0);
		}
	}

	public int findFloor(int xSprite) {
		final int xMap = xSprite / imWidth;
		int locY = panelHeight;
		final List<Brick> column = columnBricks[xMap];
		Brick b;
		for (int i = 0; i < column.size(); i++) {
			b = column.get(i);
			if (b.getYLocation() < locY) {
				locY = b.getYLocation();
			}
		}
		return locY;
	}

	public int getBrickHeight() {
		return imageHeight;
	}

	public int getMoveSize() {
		return moveSize;
	}

	public boolean insideBrick(int xWorld, int yWorld) {
		final Point mapCoord = worldToMap(xWorld, yWorld);
		final List<Brick> column = columnBricks[mapCoord.x];
		Brick b;
		for (int i = 0; i < column.size(); i++) {
			b = column.get(i);
			if (mapCoord.y == b.getMapY()) {
				return true;
			}
		}
		return false;
	}

	public void moveLeft() {
		isMovingRight = false;
		isMovingLeft = true;
	}

	public void moveRight() {
		isMovingRight = true;
		isMovingLeft = false;
	}

	public void stayStill() {
		isMovingRight = false;
		isMovingLeft = false;
	}

	public void update() {
		if (isMovingRight) {
			xMapHead = (xMapHead + moveSize) % width;
		} else if (isMovingLeft) {
			xMapHead = (xMapHead - moveSize) % width;
		}
	}

	private void addBrickDetails() {
		for (int i = 0; i < bricks.size(); i++) {
			Brick brick = bricks.get(i);
			BufferedImage bufferedImage = brickImages.get(brick.getImageID());
			brick.setImage(bufferedImage);
			brick.setYLocation(panelHeight, numRows);
		}
	}

	private void calcMapDimensions() {
		width = imWidth * numCols;
		height = imageHeight * numRows;
		if (width < panelWidth) {
			System.out.println("Bricks map is less wide than the panel");
			System.exit(0);
		}
	}

	private void checkForGaps() {
		final boolean[] hasBrick = new boolean[numCols];
		for (int j = 0; j < numCols; j++) {
			hasBrick[j] = false;
		}
		for (int i = 0; i < bricks.size(); i++) {
			Brick brick = bricks.get(i);
			if (brick.getMapY() == numRows - 1) {
				hasBrick[brick.getMapX()] = true;
			}
		}
		for (int j = 0; j < numCols; j++) {
			if (!hasBrick[j]) {
				System.out.println("Gap found in bricks map bottom line at position " + j);
				System.exit(0);
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void createColumns() {
		columnBricks = new ArrayList[numCols];
		for (int i = 0; i < numCols; i++) {
			columnBricks[i] = new ArrayList<Brick>();
		}
		for (int j = 0; j < bricks.size(); j++) {
			final Brick b = bricks.get(j);
			columnBricks[b.getMapX()].add(b);
		}
	}

	private void drawBricks(Graphics g, int xStart, int xEnd, int xBrick) {
		int xMap = xBrick / imWidth;
		for (int x = xStart; x < xEnd; x += imWidth) {
			final List<Brick> column = columnBricks[xMap];
			for (int i = 0; i < column.size(); i++) {
				final Brick b = column.get(i);
				b.display(g, x);
			}
			xMap++;
		}
	}

	private void findNumBricks() {
		numCols = 0;
		numRows = 0;
		for (int i = 0; i < bricks.size(); i++) {
			final Brick b = bricks.get(i);
			if (numCols < b.getMapX()) {
				numCols = b.getMapX();
			}
			if (numRows < b.getMapY()) {
				numRows = b.getMapY();
			}
		}
		numCols++;
		numRows++;
	}

	private String getPrefix(String fileName) {
		int position;
		if ((position = fileName.lastIndexOf(".")) == -1) {
			System.out.println("No prefix found for filename: " + fileName);
			return fileName;
		} else {
			return fileName.substring(0, position);
		}
	}

	private int getStripImages(String line) {
		final StringTokenizer tokens = new StringTokenizer(line);
		if (tokens.countTokens() != 3) {
			System.out.println("Wrong no. of arguments for " + line);
			return -1;
		} else {
			tokens.nextToken();
			System.out.print("Bricks strip: ");
			final String fileName = tokens.nextToken();
			int number = -1;
			try {
				number = Integer.parseInt(tokens.nextToken());
				imagesLoader.loadStripImages(fileName, number);
				brickImages = imagesLoader.getImages(getPrefix(fileName));
			} catch (final Exception e) {
				System.out.println("Number is incorrect for " + line);
			}
			return number;
		}
	}

	private void initBricksInfo() {
		if (brickImages == null) {
			System.out.println("No bricks images were loaded");
			System.exit(1);
		}
		if (bricks.size() == 0) {
			System.out.println("No bricks map were loaded");
			System.exit(1);
		}
		final BufferedImage im = brickImages.get(0);
		imWidth = im.getWidth();
		imageHeight = im.getHeight();
		findNumBricks();
		calcMapDimensions();
		checkForGaps();
		addBrickDetails();
	}

	private void loadBricksFile(String fileName) {
		final String imagesFileNam = IMAGE_DIR + fileName;
		System.out.println("Reading bricks file: " + imagesFileNam);
		int numStripImages = -1;
		int numBricksLines = 0;
		try {
			final BufferedReader reader = new BufferedReader(new FileReader(imagesFileNam));
			String line;
			while ((line = reader.readLine()) != null) {
				if (line.length() == 0 || line.startsWith("//")) {
					continue;
				}
				final char ch = Character.toLowerCase(line.charAt(0));
				if (ch == 's') {
					numStripImages = getStripImages(line);
				} else {
					if (numBricksLines > MAX_BRICKS_LINES) {
						System.out.println("Max reached, skipping bricks line: " + line);
					} else if (numStripImages == -1) {
						System.out.println("No strip image, skipping bricks line: " + line);
					} else {
						storeBricks(line, numBricksLines, numStripImages);
						numBricksLines++;
					}
				}
			}
			reader.close();
		} catch (final IOException e) {
			System.out.println("Error reading file: " + imagesFileNam);
			System.exit(1);
		}
	}

	private void storeBricks(String line, int lineNo, int numImages) {
		for (int x = 0; x < line.length(); x++) {
			final char ch = line.charAt(x);
			if (ch == ' ') {
				continue;
			}
			if (Character.isDigit(ch)) {
				final int imageID = ch - '0';
				if (imageID >= numImages) {
					System.out.println("Image ID " + imageID + " out of range");
				} else {
					bricks.add(new Brick(imageID, x, lineNo));
				}
			} else {
				System.out.println("Brick char " + ch + " is not a digit");
			}
		}
	}

	private Point worldToMap(int xWorld, int yWorld) {
		xWorld = xWorld % width;
		if (xWorld < 0) {
			xWorld += width;
		}
		final int mapX = xWorld / imWidth;
		yWorld = yWorld - (panelHeight - height);
		int mapY = yWorld / imageHeight;
		if (yWorld < 0) {
			mapY = mapY - 1;
		}
		return new Point(mapX, mapY);
	}
}